在昨天的文章中提到過,Shader 是運行在 GPU 上的小程式。這些小程式為 Render Pipline 的某個特定部分運行。基本上來說,Shader 只是把一種輸入轉化為輸出的程式。而 Shader 之間的溝通只有透過輸入和輸出。
GLSL(OpenGL Shading Language Language)是 OpenGL 的 Shader 語言,它長得有點像C語言,一個 Shader 通常長這樣:
#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
int main()
{
// 處理輸入並進行一些圖形操作
...
// 輸出處理過的結果到輸出變數
out_variable_name = weird_stuff_we_processed;
}
當特別談到 Vertex Shader 的時候,每個輸入輛每個輸入的變數也較做頂點屬性(Vertex Attribute),OpenGL 通常保證有 16 個的頂點屬性可以使用,在大多數情況都夠用。也可以使用GL_MAX_VERTEX_ATTRIBS
來查詢具體的上限。
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
GLSL 有C語言及其他語言大多都有的 type:int
, float
, double
, uint
, bool
,另位還有兩種容器 type: 向量(Vector)跟矩陣(Matrix)
向量
vecn
包含 n 個 float
bvecn
包含 n 個 bool
ivecn
包含 n 個 int
uvecn
包含 n 個 unsigned int
dvecn
包含 n 個 double
分量用 .x
, .y
, .z
, .w
來存取分量,或是rgba
(顏色)跟stpq
(材質)來存取。
重組(Swizzling)
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
我們希望每個 Shader 都有輸入以及輸出,這樣才能與其他 Shader 溝通。
GLSL 可以用 in
, out
來標示一個變數是傳入還是傳出,上個 Shader 所指定的 out
會對應到下個 Shader 所指定的 in
變數,名稱要相同。
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 vertexColor; // to Fragment Shader
void main()
{
gl_Position = vec4(aPos, 1.0);
vertexColor = vec4(0.5, 0.0, 0.0, 1.0)
}
#version 330 core
in vec3 vertexColor; // from Vertex Shader
out vec4 FragColor;
void main()
{
FragColor = vertexColor;
}
我們在 Vertex Shader 宣告一個 vertexColor
的變數作為 vec4
的輸出,而在 Fragment Shader 也宣告了類似的 vertexColor
。 由於名字相同,Fragment Shader 中的 vertexColor
就與 Vertex Shader 連接了。
有些人可能會想,我們能否直接從程式碼中給 Fragment Shader 發送顏色呢?
uniform 是 CPU 向 GPU 的 Shader 發送數據的方式,但跟 Vertex Attribute 有點不同。
uniform 可以在所有的 Shader 中存取,而不用像 Vertex Attribute 是要用 in
, out
傳資料,意思是 uniform 是 Global 的變數,它必須是 獨特的(Unique) 的。
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // 在 Code 中傳入
void main()
{
FragColor = ourColor;sd
}
目前這個 uniform
是空的,我們需要去給他設定值。
我們首先要找到 Shader 中 uniform
的索引/位置值之後,我們就可以更新他的值。
這次我不讓他固定顏色,我讓她隨時間變化顏色。
float t, blue;
while (running)
{
sf::Event event;
// SFML Event handle
[...]
// Render
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Pass the blue value
t = clk.getElapsedTime().asSeconds();
blue = (sin(t) / 2.f) + 0.5f;
glUseProgram(program);
glUniform4f(vertexColorLocation, 0.0f, 0.f, blue, 1.0f);
// Bind Vao and Draw
[...]
// SFML display
[...]
glUniform
是個典型的例子。這個function
後面都會接有一個特定的後綴,表示設定的 typefloat
int
unsigned int
float
array of float
如今我們希望將顏色的數據也放到 Vertex Data 中。
float vertices[] = {
// 位置 // 顏色
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 上方
};
因為有更多的數據發送到 Shader 了,所以需要調整下 Shader。
Vertex Shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 ourColor; // 輸出到 Fragment Shader
void main() {
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
}
Fragment Shader
#version 330 core
out vec4 FragColor;
in vec3 ourColor; // 從 Vertex Shader 輸入的顏色,名稱要一樣
void main() {
FragColor = ourColor;
}
由於我們更新了頂點資料,並且更新了 VBO,所以我們需要重新配置頂點屬性的 pointer。
// Position index,size, type, normalize, stride, offset
glVertexPointerAttrib(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Color
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
由於我們現在有了兩個頂點屬性,我們不得不重新計算步長值。為獲得數據隊列中下一個屬性值(比如位置向量的下個x分量)我們必須向右移動6個float,其中3個是位置值,另外3個是顏色值。這使我們的步長值為6乘以float的字節數(24)。
同樣,這次我們必須指定一個偏移量。對於每個頂點來說,位置頂點屬性在前,所以它的偏移量是0。顏色屬性緊隨位置數據之後,所以偏移量就是3 * sizeof(float),用字節來計算就是12字節。
https://learnopengl.com/Getting-started/Shaders